/**
 * We want to leverage the TestNG XMLReporter code, but it's not written in a
 * way that's easy to do so. XMLReporter has a bunch of private methods and is
 * not written with DI in mind. Therefore, we start with a copy of it, and
 * modify it to suit our needs. In particular, the reporter needs to support
 * reporting on task results.
 */
package com.googlecode.lightest.core

import org.testng.*
import org.testng.internal.Utils
import org.testng.ITestResult
import org.testng.reporters.XMLReporterConfig
import org.testng.reporters.XMLStringBuffer
import org.testng.xml.XmlSuite

class XMLReporter2 {
    XMLReporterConfig config = new XMLReporterConfig()
    XMLStringBuffer rootBuffer

    def taskResultMap
    def suiteResultWriter
    
    XMLReporter2() {
        suiteResultWriter = new XMLSuiteResultWriter2(config)
    }
    
    void setTaskResultMap(Map<ITestResult, List<ITaskResult>> taskResultMap) {
        this.taskResultMap = taskResultMap
    }
    
    /**
     * Sets the suite result writer. This object must implement the following
     * public methods: setConfig(), setTaskResultMap(), writeSuiteResult(), and
     * addTestMethodParams(). See the implementation of XMLSuiteResultWriter2
     * for reference.
     *
     * @param suiteResultWriter
     */
    void setSuiteResultWriter(suiteResultWriter) {
        this.suiteResultWriter = suiteResultWriter
        this.suiteResultWriter.setConfig(config)
    }
    
    void generateReport(List<XmlSuite> xmlSuites, List<ISuite> suites, String outputDirectory) {
        if (Utils.isStringEmpty(config.getOutputDirectory())) {
            config.setOutputDirectory(outputDirectory)
        }
        
        rootBuffer = new XMLStringBuffer2("")
        rootBuffer.push(XMLReporterConfig.TAG_TESTNG_RESULTS)
        writeReporterOutput(rootBuffer)
        for (i in 0..<suites.size()) {
            writeSuite(xmlSuites.get(i), suites.get(i))
        }
        rootBuffer.pop()
        Utils.writeUtf8File(config.getOutputDirectory(), "testng-results.xml",
            '<?xml-stylesheet type="text/xsl" href="../suite.xsl"?>\n' +
            rootBuffer.toXML())
    }
    
    private void writeReporterOutput(XMLStringBuffer xmlBuffer) {
        xmlBuffer.push(XMLReporterConfig.TAG_REPORTER_OUTPUT)
        List<String> output = Reporter.getOutput()
        for (String line : output) {
            if (line != null) {
                xmlBuffer.push(XMLReporterConfig.TAG_LINE)
                xmlBuffer.addCDATA(line)
                xmlBuffer.pop()
            }
        }
        xmlBuffer.pop()
    }

    private void writeSuite(XmlSuite xmlSuite, ISuite suite) {
        switch (config.getFileFragmentationLevel()) {
            case XMLReporterConfig.FF_LEVEL_NONE:
                writeSuiteToBuffer(rootBuffer, suite)
                break
            case XMLReporterConfig.FF_LEVEL_SUITE:
            case XMLReporterConfig.FF_LEVEL_SUITE_RESULT:
                File suiteFile = referenceSuite(rootBuffer, suite)
                writeSuiteToFile(suiteFile, suite)
        }
    }

    private void writeSuiteToFile(File suiteFile, ISuite suite) {
        XMLStringBuffer xmlBuffer = new XMLStringBuffer2("")
        writeSuiteToBuffer(xmlBuffer, suite)
        File parentDir = suiteFile.getParentFile()
        if (parentDir.exists() || suiteFile.getParentFile().mkdirs()) {
            Utils.writeFile(parentDir.getAbsolutePath(), "testng-results.xml", xmlBuffer.toXML())
        }
    }

    private File referenceSuite(XMLStringBuffer xmlBuffer, ISuite suite) {
        String relativePath = suite.getName() + File.separatorChar + "testng-results.xml"
        File suiteFile = new File(config.getOutputDirectory(), relativePath)
        Properties attrs = new Properties()
        attrs.setProperty(XMLReporterConfig.ATTR_URL, relativePath)
        xmlBuffer.addEmptyElement(XMLReporterConfig.TAG_SUITE, attrs)
        return suiteFile
    }

    private void writeSuiteToBuffer(XMLStringBuffer xmlBuffer, ISuite suite) {
        assert suiteResultWriter != null
        
        suiteResultWriter.setTaskResultMap(taskResultMap)
        
        xmlBuffer.push(XMLReporterConfig.TAG_SUITE, getSuiteAttributes(suite))
        writeSuiteGroups(xmlBuffer, suite)

        Map<String, ISuiteResult> results = suite.getResults()
        for (Map.Entry<String, ISuiteResult> result : results.entrySet()) {
            suiteResultWriter.writeSuiteResult(xmlBuffer, result.getValue())
        }

        xmlBuffer.pop()
    }

    private void writeSuiteGroups(XMLStringBuffer xmlBuffer, ISuite suite) {
        xmlBuffer.push(XMLReporterConfig.TAG_GROUPS)
        Map<String, Collection<ITestNGMethod>> methodsByGroups = suite.getMethodsByGroups()
        for (String groupName : methodsByGroups.keySet()) {
            Properties groupAttrs = new Properties()
            groupAttrs.setProperty(XMLReporterConfig.ATTR_NAME, groupName)
            xmlBuffer.push(XMLReporterConfig.TAG_GROUP, groupAttrs)
            Set<ITestNGMethod> groupMethods = getUniqueMethodSet(methodsByGroups.get(groupName))
            for (ITestNGMethod groupMethod : groupMethods) {
                Properties methodAttrs = new Properties()
                methodAttrs.setProperty(XMLReporterConfig.ATTR_NAME, groupMethod.getMethodName())
                methodAttrs.setProperty(XMLReporterConfig.ATTR_METHOD_SIG, groupMethod.toString())
                methodAttrs.setProperty(XMLReporterConfig.ATTR_CLASS, groupMethod.getRealClass().getName())
                xmlBuffer.addEmptyElement(XMLReporterConfig.TAG_METHOD, methodAttrs)
            }
            xmlBuffer.pop()
        }
        xmlBuffer.pop()
    }

    private Properties getSuiteAttributes(ISuite suite) {
        Properties props = new Properties()
        props.setProperty(XMLReporterConfig.ATTR_NAME, suite.getName())
        return props
    }

    private Set<ITestNGMethod> getUniqueMethodSet(Collection<ITestNGMethod> methods) {
        Set<ITestNGMethod> result = new LinkedHashSet<ITestNGMethod>()
        for (ITestNGMethod method : methods) {
            result.add(method)
        }
        return result
    }

    int getFileFragmentationLevel() {
        return config.getFileFragmentationLevel()
    }

    void setFileFragmentationLevel(int fileFragmentationLevel) {
        config.setFileFragmentationLevel(fileFragmentationLevel)
    }

    int getStackTraceOutputMethod() {
        return config.getStackTraceOutputMethod()
    }

    void setStackTraceOutputMethod(int stackTraceOutputMethod) {
        config.setStackTraceOutputMethod(stackTraceOutputMethod)
    }

    String getOutputDirectory() {
        return config.getOutputDirectory()
    }

    void setOutputDirectory(String outputDirectory) {
        config.setOutputDirectory(outputDirectory)
    }

    boolean isGenerateGroupsAttribute() {
        return config.isGenerateGroupsAttribute()
    }

    void setGenerateGroupsAttribute(boolean generateGroupsAttribute) {
        config.setGenerateGroupsAttribute(generateGroupsAttribute)
    }

    boolean isSplitClassAndPackageNames() {
        return config.isSplitClassAndPackageNames()
    }

    void setSplitClassAndPackageNames(boolean splitClassAndPackageNames) {
        config.setSplitClassAndPackageNames(splitClassAndPackageNames)
    }

    String getTimestampFormat() {
        return config.getTimestampFormat()
    }

    void setTimestampFormat(String timestampFormat) {
        config.setTimestampFormat(timestampFormat)
    }

    boolean isGenerateDependsOnMethods() {
        return config.isGenerateDependsOnMethods()
    }

    void setGenerateDependsOnMethods(boolean generateDependsOnMethods) {
        config.setGenerateDependsOnMethods(generateDependsOnMethods)
    }

    void setGenerateDependsOnGroups(boolean generateDependsOnGroups) {
        config.setGenerateDependsOnGroups(generateDependsOnGroups)
    }

    boolean isGenerateDependsOnGroups() {
        return config.isGenerateDependsOnGroups()
    }
}
